package ip.exceptions;

import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.rmi.RemoteException;
import java.sql.SQLException;

import org.xml.sax.SAXException;

public class ApplicationException extends Exception {


	private static final long serialVersionUID = -2375097144391130111L;
	/** A wrapped Throwable */
	protected Throwable cause;

	public ApplicationException() {
		super("Error occurred in application.");
	}

	public ApplicationException(String message)  {
		super(message);
	}

	public ApplicationException(String message, Throwable cause)  {
		super(message);
		this.cause = cause;
	}

	// Created to match the JDK 1.4 Throwable method.
	public Throwable initCause(Throwable cause)  {
		this.cause = cause;
		return cause;
	}

	public String getMessage() {
		// Get this exception's message.
		String msg = super.getMessage();

		Throwable parent = this;
		Throwable child;

		// Look for nested exceptions.
		while((child = getNestedException(parent)) != null) {
			// Get the child's message.
			String msg2 = child.getMessage();

			// If we found a message for the child exception, 
			// we append it.
			if (msg2 != null) {
				if (msg != null) {
					msg += ": " + msg2;
				} else {
					msg = msg2;
				}
			}

			// Any nested ApplicationException will append its own
			// children, so we need to break out of here.
			if (child instanceof ApplicationException) {
				break;
			}
			parent = child;
		}

		// Return the completed message.
		return msg;
	}

	public void printStackTrace() {
		// Print the stack trace for this exception.
		super.printStackTrace();

		Throwable parent = this;
		Throwable child;

		// Print the stack trace for each nested exception.
		while((child = getNestedException(parent)) != null) {
			if (child != null) {
				System.err.print("Caused by: ");
				child.printStackTrace();

				if (child instanceof ApplicationException) {
					break;
				}
				parent = child;
			}
		}
	}

	public void printStackTrace(PrintStream s) {
		// Print the stack trace for this exception.
		super.printStackTrace(s);

		Throwable parent = this;
		Throwable child;

		// Print the stack trace for each nested exception.
		while((child = getNestedException(parent)) != null) {
			if (child != null) {
				s.print("Caused by: ");
				child.printStackTrace(s);

				if (child instanceof ApplicationException) {
					break;
				}
				parent = child;
			}
		}
	}

	public void printStackTrace(PrintWriter w) {
		// Print the stack trace for this exception.
		super.printStackTrace(w);

		Throwable parent = this;
		Throwable child;

		// Print the stack trace for each nested exception.
		while((child = getNestedException(parent)) != null) {
			if (child != null) {
				w.print("Caused by: ");
				child.printStackTrace(w);

				if (child instanceof ApplicationException) {
					break;
				}
				parent = child;
			}
		}
	}

	public Throwable getCause()  {
		return cause;
	}
	
    // If this Throwable has a nested (child) exception, then we return it.
    // Otherwise we return null.
    private static Throwable getNestedException(Throwable parent) {      
        if (parent instanceof SAXException) {
            return ((SAXException)parent).getException();
        }
        
        if (parent instanceof SQLException) {
            return ((SQLException)parent).getNextException();
        }
        
        if (parent instanceof InvocationTargetException) {
            return ((InvocationTargetException)parent).getTargetException();
        }
        
        if (parent instanceof ExceptionInInitializerError) {
            return ((ExceptionInInitializerError)parent).getException();
        }
        
        if (parent instanceof RemoteException) {
            return ((RemoteException)parent).detail;
        }
        
        // These classes are not part of standard JDK 1.1 or 1.2, so we 
        // use reflection to access them.

        Throwable nestedException = getNestedException(parent, "javax.naming.NamingException", "getRootCause");
        if (nestedException != null) {
            return nestedException;
        }
        
        nestedException = getNestedException(parent, "javax.servlet.ServletException", "getRootCause");
        if (nestedException != null) {
            return nestedException;
        }

        return null;
    }

    // This method uses reflection to obtain the nest exception of a Throwable. We use reflection
    // because the desired class may not exist in the currently-running VM.
    private static Throwable getNestedException(
                                 Throwable parent, String className, String methodName) {
        try {
            // See if this Throwable is of the desired type, by using isAssignableFrom().
            Class testClass = Class.forName(className);
            Class objectClass = parent.getClass();
            if (testClass.isAssignableFrom(objectClass)) {
                // Use reflection to call the specified method.
                Class[] argClasses = new Class[0];
                Method method = testClass.getMethod(methodName, argClasses);
                Object[] args = new Object[0];
                return (Throwable)method.invoke(parent, args);
            }
        }
        catch(Exception ex) {
            // Most likely, the desired class is not available in this VM. That's fine.
            // Even if it's caused by something else, we don't want to display an error
            // here, since we're already in the process of trying to display the original
            // error - another error here will just confuse things.
        }

        return null;
    }
}

